package com.zhangmeng.tools.controller;

import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.io.resource.Resource;
import com.zhangmeng.tools.components.RecursiveFileList;
import com.zhangmeng.tools.languages.*;
import com.zhangmeng.tools.utils.AlertUtils;
import com.zhangmeng.tools.utils.FxmlUtils;
import javafx.application.Platform;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.text.Font;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
import javafx.util.Callback;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
import org.fxmisc.richtext.model.StyleSpans;
import org.fxmisc.richtext.model.StyleSpansBuilder;

import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author : 芊芊墨客
 * @version : 1.0
 * @date : 2023-06-01 10:44
 * <p>
 * 文本编辑器
 */
@Slf4j
public class FileEditController {

    @FXML
    public MenuItem save;

    @FXML
    public MenuItem open_file;

    @FXML
    public MenuItem open_dir;

    public enum Type {

        JAVA,
        HTML,
        GO,
        JS,
        CSS,
        SQL,
        XML,
        YML
    }

    @FXML
    public SplitPane splitPane;

    private RecursiveFileList recursiveFileList = null;

    private TabPane tabPane = null;
    private final ObservableList<Path> filePaths_list = FXCollections.observableArrayList();
    private final SimpleIntegerProperty text_size = new SimpleIntegerProperty(15);
    private final SimpleIntegerProperty icon_size = new SimpleIntegerProperty(15);
    private static final Map<Path, Tab> map = new HashMap<>();

    @FXML
    public void initialize() {

        // 设置快捷键
        save.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCodeCombination.CONTROL_DOWN));
        open_file.setAccelerator(new KeyCodeCombination(KeyCode.F, KeyCodeCombination.CONTROL_DOWN));
        open_dir.setAccelerator(new KeyCodeCombination(KeyCode.D, KeyCodeCombination.CONTROL_DOWN));
        recursiveFileList = new RecursiveFileList(null);
        init_file_list();
        tabPane = new TabPane();
        splitPane.getItems().add(0, recursiveFileList);
        splitPane.getItems().add(1, tabPane);
        splitPane.setDividerPosition(0, 0.20);
        splitPane.setDividerPosition(1, 0.80);
    }

    @FXML
    public void open_file_menu() {
        Stage stage = new Stage();
        FileChooser dc = new FileChooser();
        dc.setTitle("文件选择");
        dc.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("文件类型", "*.java", "*.go", "*.txt", "*.json", "*.sql"));
        File file = dc.showOpenDialog(stage);
        if (file != null) {
            refresh(file, true);
        }
    }

    //文件保存
    @FXML
    public void save_file_menu(ActionEvent event) {
        writer_file();
    }

    public void writer_file() {
        Tab selectedItem = tabPane.getSelectionModel().getSelectedItem();

        int i = tabPane.getTabs().indexOf(selectedItem);
        Path path = filePaths_list.get(i);
        log.info("writer_file__path:" + path.getFileName());

        VirtualizedScrollPane virtualizedScrollPane = (VirtualizedScrollPane) selectedItem.getContent();
        CodeArea codeArea = (CodeArea) virtualizedScrollPane.getContent();
        String text = codeArea.getText();
        try {
            FileWriter writer = new FileWriter(path.toFile());
            writer.write(text);
            writer.flush();
            writer.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        AlertUtils.alert_warning("tab " + selectedItem.getText() + "保存成功!");
    }

    public void init_file_list() {
        this.recursiveFileList.setCellFactory(new Callback<TreeView<File>, TreeCell<File>>() {
            @Override
            public TreeCell<File> call(TreeView<File> param) {
                return new TreeCell<File>() {
                    @Override
                    protected void updateItem(File item, boolean empty) {
                        super.updateItem(item, empty);
                        if (!empty) {
                            HBox hBox = new HBox(10);
                            ImageView iv = null;
                            if (this.getTreeItem().isExpanded()) {
                                iv = new ImageView(new Image("image/close.png"));
                            } else {
                                iv = new ImageView(new Image("image/open.png"));
                            }
                            iv.setPreserveRatio(true);
                            iv.setFitWidth(icon_size.get());
                            this.setDisclosureNode(iv);

                            Label label = new Label(item.getName());
                            label.setFont(new Font(text_size.get()));

                            //判断是否是文件
                            boolean directory = item.isDirectory();
                            if (directory) {
                                ImageView dir = new ImageView(new Image("image/dir.png"));
                                dir.setPreserveRatio(true);
                                dir.setFitWidth(icon_size.get());
                                hBox.getChildren().add(dir);
                            } else {
                                if (item.isFile()) {
                                    ImageView file = null;

                                    String extension = FilenameUtils.getExtension(item.getName());
                                    if (extension.equals("java")) {
                                        file = new ImageView(new Image("image/java.png"));
                                    }
                                    if (extension.equals("html")) {
                                        file = new ImageView(new Image("image/html.png"));
                                    }
                                    if (extension.equals("jar")) {
                                        file = new ImageView(new Image("image/jar.png"));
                                    }
                                    if (extension.equals("json")) {
                                        file = new ImageView(new Image("image/json.png"));
                                    }

                                    if (extension.equals("jpg")) {
                                        file = new ImageView(new Image("image/jpg.png"));
                                    }

                                    if (extension.equals("png")) {
                                        file = new ImageView(new Image("image/png.png"));
                                    }

                                    if (file != null) {
                                        file.setPreserveRatio(true);
                                        file.setFitWidth(icon_size.get());
                                        hBox.getChildren().add(file);
                                    }
                                }
                            }

                            hBox.getChildren().add(label);

                            hBox.setOnMouseClicked(new EventHandler<MouseEvent>() {
                                @Override
                                public void handle(MouseEvent event) {
                                    if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
                                        // 双击左键的操作
                                        file_checked(item);
                                    }
                                }
                            });

                            this.setGraphic(hBox);
                        } else {
                            this.setGraphic(null);
                            this.setDisclosureNode(null);
                        }
                    }
                };
            }
        });
    }

    @FXML
    public void open_file_dir_menu(ActionEvent event) {
        open_file_dir();
    }

    public void open_file(File file, Type type) {
        Path path = file.toPath();
        if (filePaths_list.contains(path)) {//size -1 ,szie -2
            //切换到一打开的tab
            Tab tab = map.get(path);
            if (!tabPane.getTabs().contains(tab)) {
                tabPane.getTabs().add(tab);
            }
            tabPane.getSelectionModel().select(tab);
        } else {
            CodeArea codeArea = new CodeArea();
            URL resource = this.getClass().getClassLoader().getResource("css/code.css");
            codeArea.getStylesheets().add(resource.toExternalForm());
            codeArea.setParagraphGraphicFactory(LineNumberFactory.get(codeArea));
            byte[] bytes = new byte[0];
//        try {
//            bytes = Files.readAllBytes(file.toPath());
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//        String file_content = new String(bytes, StandardCharsets.UTF_8);
            String file_content = null;
            try {
                file_content = Files.readString(file.toPath(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                try {
                    file_content = Files.readString(file.toPath(), Charset.forName("GBK"));
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
            if (!(FileUtils.sizeOf(file) > 10 * 1024 * 1024)) {
                codeArea.textProperty().addListener((obs, oldText, newText) -> {
                    new Thread(() -> {
                        Platform.runLater(() -> {
                            codeArea.setStyleSpans(0, computeHighlighting(newText, type));
                        });
                    }).start();
                });
            }
            codeArea.replaceText(0, 0, file_content);
            Tab tab = new Tab(file.getName());

            ContextMenu contextMenu = new ContextMenu();
            MenuItem close = new MenuItem("close");
            MenuItem close_other_tabs = new MenuItem("close other tabs");
            MenuItem close_all = new MenuItem("close all");
            close_all.setOnAction(event -> {
                tabPane.getTabs().clear();
            });
            close_other_tabs.setOnAction(event -> {
                ObservableList<Tab> tabs = tabPane.getTabs();
                ObservableList<Tab> list = FXCollections.observableArrayList(tabs);
                //关闭其他
                for (Tab tabPaneTab : list) {
                    if (tabPaneTab != tabPane.getSelectionModel().getSelectedItem()){
                        tabs.remove(tabPaneTab);
                    }
                }
            });
            close.setOnAction(event -> {
                //关闭当前窗口
                Tab selectedItem = tabPane.getSelectionModel().getSelectedItem();
                tabPane.getTabs().remove(selectedItem);
            });
            contextMenu.getItems().add(close);
            contextMenu.getItems().add(close_other_tabs);
            contextMenu.getItems().add(close_all);
            tab.setContextMenu(contextMenu);
            tab.setOnCloseRequest(new EventHandler<Event>() {
                @Override
                public void handle(Event event) {
//                    log.info("tabtab.setOnCloseRequest:--->" + tab.getText());
                    tabPane.getTabs().remove(tab);
                }
            });
            tab.setContent(new VirtualizedScrollPane<>(codeArea));
            filePaths_list.add(path);
            tabPane.getTabs().add(tab);
            tabPane.getSelectionModel().select(tab);
            map.put(path, tab);
        }
    }

    public void open_file_dir() {

        Stage stage = new Stage();
        DirectoryChooser dc = new DirectoryChooser();
        dc.setTitle("文件夹选择");
        File file = dc.showDialog(stage);
        if (file != null) {
            if (file.isDirectory()) {
                refresh(file, false);
            }
        }
    }

    public void refresh(File file, boolean is_open_file) {
        splitPane.getItems().clear();
        recursiveFileList = new RecursiveFileList(file);
        init_file_list();
        recursiveFileList.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue.getValue().isFile()) {
                file_checked(newValue.getValue());
            }
        });
        splitPane.getItems().add(0, recursiveFileList);
        splitPane.getItems().add(1, tabPane);
        splitPane.setDividerPosition(0, 0.20);
        splitPane.setDividerPosition(1, 0.80);
        if (is_open_file) {
            file_checked(file);
        }
    }

    public void file_checked(File file) {
        String extension = FilenameUtils.getExtension(file.getName());
        boolean flag = true;

        if (extension.equals("mp4")) {
            flag = false;
        }

        if (extension.equals("jpg")) {
            flag = false;
        }

        if (extension.equals("png")) {
            flag = false;
        }

        if (extension.equals("avi")) {
            flag = false;
        }

        if (extension.equals("zip")) {
            flag = false;
        }

        if (extension.equals("ico")) {
            flag = false;
        }

        if (extension.equals("exe")) {
            flag = false;
        }
        if (flag) {
            Type type = null;

            if (extension.equals("java")) {
                type = Type.JAVA;
            }

            if (extension.equals("go")) {
                type = Type.GO;
            }
            if (extension.equals("html")) {
                type = Type.HTML;
            }
            if (extension.equals("sql")) {
                type = Type.SQL;
            }
            if (extension.equals("xml")) {
                type = Type.XML;
            }
            if (extension.equals("yml")) {
                type = Type.YML;
            }
            if (type == null) {
                type = Type.JAVA;
            }
            open_file(file, type);
        } else {
            AlertUtils.alert_warning("该文件不支持!");
        }
    }

    public static StyleSpans<Collection<String>> computeHighlighting(String text, Type type) {
        Matcher matcher = ProcessJava.PATTERN.matcher(text);
        if (type.equals(Type.GO)) {
            matcher = ProcessGO.PATTERN.matcher(text);
        }
        if (type.equals(Type.HTML)) {
            matcher = ProcessHtml.PATTERN.matcher(text);
        }
        if (type.equals(Type.SQL)) {
            matcher = ProcessSql.PATTERN.matcher(text);
        }
        if (type.equals(Type.XML)) {
            matcher = ProcessXML.PATTERN.matcher(text);
        }
        if (type.equals(Type.YML)) {
            matcher = ProcessYML.PATTERN.matcher(text);
        }
        int lastKwEnd = 0;
        StyleSpansBuilder<Collection<String>> spansBuilder = new StyleSpansBuilder<>();
        while (matcher.find()) {
            String styleClass = "";

            if (type.equals(Type.JAVA)) {
                styleClass = matcher.group("KEYWORD") != null ? "keyword" :
                        matcher.group("PAREN") != null ? "paren" :
                                matcher.group("BRACE") != null ? "brace" :
                                        matcher.group("BRACKET") != null ? "bracket" :
                                                matcher.group("SEMICOLON") != null ? "semicolon" :
                                                        matcher.group("STRING") != null ? "string" :
                                                                matcher.group("COMMENT") != null ? "comment" :
                                                                        matcher.group("ANNOTATION") != null ? "annotation" :
                                                                                matcher.group("PARAMS") != null ? "parameter" :
                                                                                        matcher.group("METHOD") != null ? "method" :
                                                                                                matcher.group("KEYWORD2") != null ? "method2" :
                                                                                                        null; /* never happens */
            }
            if (type.equals(Type.GO)) {
                styleClass = matcher.group("KEYWORD") != null ? "keyword" :
                        matcher.group("PAREN") != null ? "paren" :
                                matcher.group("BRACE") != null ? "brace" :
                                        matcher.group("BRACKET") != null ? "bracket" :
                                                matcher.group("SEMICOLON") != null ? "semicolon" :
                                                        matcher.group("STRING") != null ? "string" :
                                                                matcher.group("COMMENT") != null ? "comment" :
                                                                        matcher.group("ANNOTATION") != null ? "annotation" :
                                                                                matcher.group("PARAMS") != null ? "parameter" :
                                                                                        matcher.group("METHOD") != null ? "method" :
                                                                                                matcher.group("KEYWORD2") != null ? "method2" :
                                                                                                        null; /* never happens */
            }

            if (type.equals(Type.HTML)) {
                styleClass = matcher.group("PAREN") != null ? "paren" :
                        matcher.group("BRACE") != null ? "brace" :
                                matcher.group("BRACKET") != null ? "bracket" :
                                        matcher.group("SEMICOLON") != null ? "semicolon" :
                                                matcher.group("STRING") != null ? "string" :
                                                        matcher.group("COMMENT") != null ? "comment" :
                                                                matcher.group("ANNOTATION") != null ? "annotation" :
                                                                        matcher.group("PARAMS") != null ? "parameter" :
                                                                                matcher.group("METHOD") != null ? "method" :
                                                                                        matcher.group("HTMLKEYWORDS") != null ? "html-keyword" : //html 标签
                                                                                                matcher.group("HTMLKEYWORDS2") != null ? "html-keyword2" : //html 属性
                                                                                                        null; /* never happens */
            }

            if (type.equals(Type.SQL)) {
                styleClass = matcher.group("PAREN") != null ? "paren" :
                        matcher.group("BRACE") != null ? "brace" :
                                matcher.group("BRACKET") != null ? "bracket" :
                                        matcher.group("SEMICOLON") != null ? "semicolon" :
                                                matcher.group("STRING") != null ? "string" :
                                                        matcher.group("COMMENT") != null ? "comment" :
                                                                matcher.group("ANNOTATION") != null ? "annotation" :
                                                                        matcher.group("PARAMS") != null ? "parameter" :
                                                                                matcher.group("METHOD") != null ? "method" :
                                                                                        matcher.group("HTMLKEYWORDS") != null ? "html-keyword" : //html 标签
                                                                                                matcher.group("HTMLKEYWORDS2") != null ? "html-keyword2" : //html 属性
                                                                                                        null; /* never happens */
            }
            if (type.equals(Type.XML)) {
                if (matcher.group("ELEMENT") != null) {
                    if (matcher.group("ELEMENT").startsWith("</")) {
                        styleClass = "xml-close-tag";
                    } else {
                        styleClass = "xml-open-tag";
                    }
                } else if (matcher.group("COMMENT") != null) {
                    styleClass = "xml-comment";
                }
            }
            if (type.equals(Type.YML)) {
                styleClass =
                        matcher.group("KEYWORD") != null ? "keyword" :
                                matcher.group("NUMBER") != null ? "number" :
                                        matcher.group("STRING") != null ? "string" :
                                                matcher.group("COMMENT") != null ? "comment" :
                                                        matcher.group("ANCHOR_ALIAS") != null ? "anchor-alias" :
                                                                matcher.group("TAG") != null ? "tag" :
                                                                        matcher.group("DOCUMENT_MARKER") != null ? "document-marker" :
                                                                                matcher.group("DIRECTIVE") != null ? "directive" :
                                                                                        matcher.group("VARIABLE") != null ? "variable" :
                                                                                                null; /* never happens */
            }

            assert styleClass != null;
            spansBuilder.add(Collections.emptyList(), matcher.start() - lastKwEnd);
            spansBuilder.add(Collections.singleton(styleClass), matcher.end() - matcher.start());
            lastKwEnd = matcher.end();
        }
        spansBuilder.add(Collections.emptyList(), text.length() - lastKwEnd);
        return spansBuilder.create();
    }

    public static String config_path() {
        Resource resource = new ClassPathResource("config/fileEdit.properties");
        InputStream inputStream = resource.getStream();
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return properties.getProperty("open.file.path");
    }

    public static Properties config_path(boolean openFile) {
        Resource resource = new ClassPathResource("config/fileEdit.properties");
        InputStream inputStream = resource.getStream();
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return properties;
    }

    public void setPath(String path, List<String> filePaths) {
        Resource resource = new ClassPathResource("config/fileEdit.properties");
        InputStream inputStream = resource.getStream();
        FileOutputStream fileOutputStream = null;
        Properties properties = new Properties();
        try {
            properties.load(inputStream);
            String value = properties.getProperty("open.file.path");
            log.info("path:{}", value);
            properties.setProperty("open.file.path", path);

            int i = 0;
            if (filePaths.size() > 0) {
                for (String filePath : filePaths) {
                    properties.setProperty("open.file.list[" + i + "]", filePath);
                    i++;
                }
            }
            log.info("properties:{}", properties);
            String out_path = Thread.currentThread().getContextClassLoader().getResource("").getPath() + "config/fileEdit.properties";
            fileOutputStream = new FileOutputStream(out_path);
            properties.store(fileOutputStream, "保存");
        } catch (IOException e) {
            log.info("选择路径出错!");
        } finally {
            try {
                inputStream.close();
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
